#include <sys/stat.h>
#include <fcntl.h>
#include "lc_four_to_six_op_Operator.h"
#include "UDP_Packet.hh"
#include "UDP6_Packet.hh"
#include "TCP_Packet.hh"
#include "TCP6_Packet.hh"
#include <iomanip>
#define BUF_SIZE 2048

void
four_to_six_op::my_install(void * data)
{
}

void * 
four_to_six_op::my_get()
{
  return NULL;
}

void
four_to_six_op::my_purge(void * data)
{
}

void 
four_to_six_op::my_init()
{

}

pktTypes::Packet *
four_to_six_op::gendata_inRequest()
{
  pktTypes::Packet * pkt = new pktTypes::Packet(new char[BUF_SIZE], BUF_SIZE);
  pktTypes::Eth_Packet eth(*pkt);
  pktTypes::IP_Packet ip(eth);
  //pktTypes::UDP_Packet udp(ip);
  pktTypes::TCP_Packet tcp(ip);

  in6_addr src;
  uint32_t src_core = (0xa << 24) | (0x1 << 16) | (0x1 << 8) | 0xb;
  src.s6_addr32[0] = src_core;
  src.s6_addr32[1] = 0;
  src.s6_addr32[2] = 0;
  src.s6_addr32[3] = 0;
  in6_addr dst;
  uint32_t dst_core = (0xa << 24) | (0x1 << 16) | (0x1 << 8) | 0x3;
  dst.s6_addr32[0] = dst_core;
  dst.s6_addr32[1] = 0;
  dst.s6_addr32[2] = 0;
  dst.s6_addr32[3] = 0;

  ip.validate(true);
  ip.setTotLen(100 + pktTypes::IPv4_HSIZE);
  ip.setTtl(255);
  ip.setProt(IPPROTO_UDP);
  ip.setFragOff(0);
  ip.setId(0);
  ip.setTos(0);
  
  // get source/dest addresses from flow state

  ip.setSrcAddr(src_core);
  ip.setDstAddr(dst_core);
  ip.chksum();

  // transfer tcp values
  tcp.setSrcPort(htons(6000));
  tcp.setDstPort(htons(5000));
  tcp.setSeqNum(htonl(1000));
  tcp.setAckSeqNum(htonl(1000));
  tcp.setDataOff(0);
  tcp.setFin(false);
  tcp.setSyn(false);
  tcp.setRst(false);
  tcp.setPsh(false);
  tcp.setAck(false);
  tcp.setUrg(false);
  tcp.setWindow(0);
  tcp.setUrgPtr(0);
  tcp.chksum();

//   udp.setSrcPort(6000);
//   udp.setDstPort(5000);
//   udp.setUDPLength(100);
//   udp.chksum();

  lagniappe::FlowID fid = pkt->getFiveTupleID();
  tt::SixFiveTuple ft;
  ft.saddr = src;
  ft.daddr = dst;
  ft.sport = htons(6000);
  ft.dport = htons(5000);
  ft.proto = IPPROTO_UDP;
  SixMap_p sm(new SixMap(getNewMutex()));
  sm->setCore(ft);
  flowMapping[fid] = sm;

  return pkt;
}

requestTypes::ConnInfo * 
four_to_six_op::gennew_connection_inRequest()
{
  requestTypes::ConnInfo * conn = new requestTypes::ConnInfo();
  return conn;
}

void 
four_to_six_op::newConnection(requestTypes::ConnInfo * data, unsigned long dataSize)
{
  tt::FiveTuple ft = data->v4;
  tt::SixFiveTuple sft = data->v6;
  uint16_t temp = ft.sport;
  ft.sport = ft.dport;
  ft.dport = temp;
  uint32_t temp2 = ft.saddr;
  ft.saddr = ft.daddr;
  ft.daddr = temp2;
  lagniappe::FlowID fid = pktTypes::Packet::getFiveTupleID(ft);
  in6_addr temp6 = sft.saddr;
  sft.saddr = sft.daddr;
  sft.daddr = temp6;
  temp = sft.sport;
  sft.sport = sft.dport;
  sft.dport = temp;
  SixMap_p sm(new SixMap(getNewMutex()));
  sm->setCore(sft);
  flowMapping[fid] = sm;
}

void 
four_to_six_op::handleMyPort(pktTypes::Packet * data, unsigned long dataSize)
{
  assert(data != NULL);

  lagniappe::FlowID fid = getFlowID(data);

  pktTypes::Eth_Packet eth(*data);
  pktTypes::IP_Packet ip(eth);
  //pktTypes::UDP_Packet udp(ip);
  pktTypes::TCP_Packet tcp(ip);
  
  pktTypes::Packet * pkt = new pktTypes::Packet(new char[BUF_SIZE], BUF_SIZE);
  memset((void *)pkt->getPayload(), '\0', BUF_SIZE);
  pktTypes::Eth_Packet eth6(*pkt);
  pktTypes::IP6_Packet ip6(eth6);
  //pktTypes::UDP6_Packet udp6(ip);
  pktTypes::TCP6_Packet tcp6(ip6);


  if(!flowMapping[fid]) {
    // unsolicited packet, drop
    //std::cerr << "Dropping packet" << std::endl;
    if(!inProf) {
      delete[] data->getPayload();
      delete data;
    }
  }
  else {
    SixMap_p fm(flowMapping[fid]);
    
    tt::SixFiveTuple sixFiveTuple = fm->getCore();
    
    // transfer ip values
    
    ip6.setIPVersion();
    ip6.setPayloadLength(ip.getTotLen() - pktTypes::IPv4_HSIZE);
    ip6.setNextHeader(sixFiveTuple.proto);
    ip6.setHopLimit(ip.getTtl() - 1);
    
    // get source/dest addresses from flow state

    ip6.setSourceAddress(sixFiveTuple.saddr);
    ip6.setDestinationAddress(sixFiveTuple.daddr);
    
    // transfer tcp values
    tcp6.setSrcPort(tcp.getSrcPort());
    tcp6.setSeqNum(tcp.getSeqNum());
    tcp6.setAckSeqNum(tcp.getAckSeqNum());
    tcp6.setDataOff(tcp.getDataOff());
    tcp6.setFin(tcp.getFin());
    tcp6.setSyn(tcp.getSyn());
    tcp6.setRst(tcp.getRst());
    tcp6.setPsh(tcp.getPsh());
    tcp6.setAck(tcp.getAck());
    tcp6.setUrg(tcp.getUrg());
    tcp6.setWindow(tcp.getWindow());
    tcp6.setUrgPtr(tcp.getUrgPtr());
    tcp6.setRes2(tcp.getRes2());

//     udp6.setSrcPort(udp.getSrcPort());
//     udp6.setUDPLength(udp.getUDPLength());
    
    // get source from flow state
    
    tcp6.setDstPort(sixFiveTuple.dport);
    //tcp6.setDstPort(tcp.getDstPort());

    //udp6.setDstPort(sixFiveTuple.dport);
    
    // copy payload
    
    //memcpy(udp6.getPayload(), udp.getPayload(), ip.getTotLen() - (pktTypes::UDP_HSIZE + pktTypes::IPv4_HSIZE));
    memcpy(tcp6.getPayload(), tcp.getPayload(), ip.getTotLen() - (pktTypes::UDP_HSIZE + pktTypes::IPv4_HSIZE));
    
    // calculate checksum
    
    tcp6.chksum();
    //udp6.chksum();
    
    pkt->setLength(pktTypes::ETH_HSIZE + ip6.getPayloadLength() + pktTypes::IPv6_HSIZE);


    pktTypes::ethAddr ea;
    
    //00:15:17:62:9D:7F
    
    ea.data[5] = 0x7F;
    ea.data[4] = 0x9D;
    ea.data[3] = 0x62;
    ea.data[2] = 0x17;
    ea.data[1] = 0x15;
    ea.data[0] = 0x00;
    
    eth6.setSrcAddress(ea);
    
    //00:04:23:BD:5B:F1
    
    ea.data[5] = 0x3A;
    ea.data[4] = 0x55;
    ea.data[3] = 0x63;
    ea.data[2] = 0x17;
    ea.data[1] = 0x15;
    ea.data[0] = 0x00;
    
    eth6.setDstAddress(ea);
    
    // set IP Protocol appropriately
    
    eth6.setEthProt(ETH_P_IPV6);

    data_out(pkt, sizeof(pktTypes::Packet));
    
    if(!inProf) {
      delete[] data->getPayload();
      delete data;
    }

  }

  return;
}
